package edu.pku.sei.autounit.reflect.jdt;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.ITypeParameter;
import org.eclipse.jdt.core.JavaModelException;

import edu.pku.sei.autounit.reflect.AbstractContextType;
import edu.pku.sei.autounit.reflect.Class;
import edu.pku.sei.autounit.reflect.Interface;
import edu.pku.sei.autounit.reflect.Type;
import edu.pku.sei.autounit.reflect.TypeVariable;
import edu.pku.sei.autounit.reflect.java.JavaTypeParameter;
import edu.pku.sei.autounit.reflect.java.JavaTypeVariable;
import edu.pku.sei.autounit.util.Log;

public class JDTInterface extends AbstractContextType implements Interface{

	private IType jdtType;
	private JDTTypeFactory factory;
	private String simpleName;
	private String fullName;
	private String packageName;
	private Interface [] interfaces;

	protected JDTInterface(IType jdtType,JDTTypeFactory factory){
		this.packageName = jdtType.getPackageFragment().getElementName();
		this.jdtType = jdtType;
		this.factory = factory;
		this.fullName = jdtType.getFullyQualifiedName().replace('$','.');
		this.simpleName = jdtType.getTypeQualifiedName().replace('$','.');

		try {
			ITypeParameter []itp = jdtType.getTypeParameters();
			typeParameters = new JavaTypeParameter[itp.length];
			for(int i=0;i<itp.length;i++){
				typeParameters[i] = new JavaTypeParameter(itp[i].getElementName());
				String []bs = itp[i].getBounds();
				Type []b = new Type[bs.length];
				for(int j=0;j<bs.length;j++){
					b[j] = factory.getType(bs[j], jdtType,this);
				}
				typeParameters[i].setBounds(b);
			}
		} catch (JavaModelException e) {
			Log.gotUnexpectedExection("JDTClass", "JDTClass", "", e);
		}
	}
	
	protected JDTInterface(IType jdtType,Type []contexts,JDTTypeFactory factory){
		this.packageName = jdtType.getPackageFragment().getElementName();
		this.jdtType = jdtType;
		this.factory = factory;
		this.fullName = jdtType.getFullyQualifiedName();
		this.simpleName = jdtType.getTypeQualifiedName();

		try {
			ITypeParameter []itp = jdtType.getTypeParameters();
			typeParameters = new JavaTypeParameter[itp.length];
			for(int i=0;i<itp.length;i++){
				typeParameters[i] = new JavaTypeParameter(itp[i].getElementName());
				if(i<contexts.length)
					typeParameters[i].setContext(contexts[i]);
				
				String []bs = itp[i].getBounds();
				Type []b = new Type[bs.length];
				for(int j=0;j<bs.length;j++){
					b[j] = factory.getType(bs[j], jdtType,this);
				}
				typeParameters[i].setBounds(b);
			}
		} catch (JavaModelException e) {
			Log.gotUnexpectedExection("JDTClass", "JDTClass", "", e);
		}
	}

	
	public String getFullName() {
		return fullName;
	}

	
	public String getName(Map<String, String> map) {
		StringBuffer sb = new StringBuffer();
		if(fullName.startsWith("java.lang"))
			sb.append(simpleName);
		else{
			String f = map.get(simpleName);
			if(f == null){
				map.put(simpleName, fullName);
				sb.append(simpleName);
			}
			else if(f.equals(fullName))
				sb.append(simpleName);
			else
				sb.append(fullName);
		}

		Type [] contexts = getContexts();
		if(contexts.length == 0)
			return sb.toString();
		sb.append("<");
		
		for(int i=0;i<contexts.length-1;i++)
			sb.append(contexts[i].getName(map)+",");
		sb.append(contexts[contexts.length-1].getName(map)+">");
		return sb.toString();
	}
	
	
	public Interface[] getInterfaces() {
		if(interfaces == null){
			try {
				String[] ifs = jdtType.getSuperInterfaceNames();
				interfaces = new Interface[ifs.length];
				for(int i = 0;i<ifs.length;i++)
					interfaces[i] = (Interface)factory.getType(ifs[i], jdtType,this);
			} catch (JavaModelException e) {
				Log.gotUnexpectedExection("JDTClass", "getInterfaces", "", e);
			}
		}
		return interfaces;
	}

	
	public String getPackageName() {
		return packageName;
	}

	
	public Iterator<Class> getSubClasses() {

		try {
			ITypeHierarchy th = jdtType.newTypeHierarchy(new NullProgressMonitor());
			final IType[] its = th.getSubtypes(jdtType);
			
			Iterator<Class> r = new Iterator<Class>(){
				private int index = 0;
				private Class next;
				
				public boolean hasNext() {
					for(;index<its.length;index++){
						Class sub = getSubClass(its[index]);
						if(sub!=null){
							next = sub;
							return true;
						}
					}
					return false;
				}

				
				public Class next() {
					index++;
					return next;
				}

				
				public void remove() {
					index = 0;
				}
			};
			return r;
		} catch (JavaModelException e) {
			Log.gotUnexpectedExection("JDTClass", "getSubClasses", "", e);
		}
		return null;
	}
	
	private Class getSubClass(IType t){
		
		try {
			if (t.isInterface())
				return null;
			String [] names = t.getSuperInterfaceNames();
			String tn = null;
			for(String nn:names){
				if(nn.indexOf(simpleName)!=-1){
					tn = nn;
					break;
				}
			}
			if(tn == null)
				return null;
			Interface c = (Interface) factory.getType(tn, t, null);
			Type[] cs = c.getContexts();
			Type[] thisContexts = getContexts();
			Map<String, Type> map = new HashMap<String, Type>();
			boolean macth = true;
			if (cs.length == thisContexts.length) {
				for (int i = 0; i < cs.length; i++) {
					if (cs[i] instanceof TypeVariable)
						map.put(cs[i].getFullName(), thisContexts[i]);
					else if (!cs[i].equals(thisContexts[i])) {
						macth = false;
						break;
					}
				}
				if (macth) {
					ITypeParameter[] itp = t.getTypeParameters();
					Type[] ts = new Type[itp.length];
					for (int i = 0; i < ts.length; i++) {
						String name = itp[i].getElementName();
						Type mt = map.get(name);
						if (mt != null)
							ts[i] = mt;
						else
							ts[i] = new JavaTypeVariable(name, new Type[0]);
					}
					return (Class) factory.getType(t, ts);
				}
			}
		} catch (JavaModelException e) {
			Log.gotUnexpectedExection("JDTClass", "getSubClasses", "", e);
		}
		return null;
	}
}
